package render.graph
{
	import mx.core.UIComponent;
	
	public class AlphaAdjuster implements ExternalAdjuster
	{
		private static const MontionLimit:Number = 0.01;//0.01
		
		private var _render:GraphRender;
		private var _damperRate:Number = 0.0;
		private var _damperOn:Boolean = true;
		private var _rigidity:Number = 0.25;		
		
		private var maxMotion:Number = 0.0;
		private var fixedDisplays:Object = new Object();
		
		public function AlphaAdjuster(render:GraphRender)
		{
			_render = render;	
		}

		public function adjust():void
		{
			if( !(_damperRate < 0.1 && _damperOn && maxMotion < MontionLimit)){
				relax();
			}
		}
		private function relax():void
		{
			refreshNodes();
			
			for( var i:int = 0 ; i < 5 ; i++){
				relaxEdges();
				avoidLabels();
				moveNodes();
			}
			
			comitNodes();
		}
		private function dueOnEachNodePairs(action:Function):void
		{
			for each (var nodeFrom:Node in _render.graph.nodeArray){
				for each( var nodeTo:Node in _render.graph.nodeArray)
					if( nodeFrom != nodeTo )
						action.call(this,nodeFrom,nodeTo);
			}
		}
		private function dueOnEachNode(action:Function):void
		{
			for each (var node:Node in _render.graph.nodeArray )
				action.call(this,node);
		}
		private function dueOnEachEdge(action:Function):void
		{
			for each(var edge:Edge in _render.graph.edgeArray)
				action.call(this,edge);
		}
		private function refreshNode(node:Node):void
		{
			node.refresh();
		}
		private function refreshNodes():void
		{
			dueOnEachNode(refreshNode);
		}
		private function comitNode(node:Node):void
		{
			if(node.display.x != node.x || node.display.y != node.y )
				_render.graph.changed();
			node.commit();
		}
		private function comitNodes():void
		{
			dueOnEachNode(comitNode);
		}
		private function relaxEdge(edge:Edge):void
		{
			var dx:Number = edge.toNode.x - edge.fromNode.x;
			var dy:Number = edge.toNode.y - edge.fromNode.y;
			var d:Number = Math.sqrt( dx * dx + dy * dy);
			
			var vx:Number = dx * _rigidity / (edge.length() * 100);
			var vy:Number = dy * _rigidity / (edge.length() * 100);
			
			edge.toNode.dx = edge.toNode.dx - vx * d;
			edge.toNode.dy = edge.toNode.dy - vy * d;
			
			edge.fromNode.dx = edge.fromNode.dx + vx * d;
			edge.fromNode.dy = edge.fromNode.dy + vy * d;
			
		}
		private function relaxEdges():void
		{
			dueOnEachEdge(relaxEdge);
		} 
		private function avoidLabel(nodeFrom:Node,nodeTo:Node):void
		{
			var dx:Number = 0;
			var dy:Number = 0;
			var vx:Number = nodeFrom.x - nodeTo.x;
			var vy:Number = nodeFrom.y - nodeTo.y;
			
			var len:Number = vx*vx + vy * vy;
			
			if(len == 0 ){
				dx = Math.random();
				dy = Math.random();
			}
			else if(len < 360000){
				dx = vx / len;
				dy = vy / len;
			}
			
			var repulsionFrom:Number = _render.repulsionFactor * (nodeFrom.display.width + nodeFrom.display.height);
			var repulsionTo:Number = _render.repulsionFactor * (nodeTo.display.width + nodeTo.display.height);

			if(repulsionFrom == 0 ) repulsionFrom = 100;
			if(repulsionTo == 0) repulsionTo =100;
			
			var factor:Number =   (repulsionFrom * repulsionTo) /100 * _rigidity;
			
			nodeFrom.dx += dx * factor;
			nodeFrom.dy += dy * factor;
			
			nodeTo.dx -= dx * factor;
			nodeTo.dy -= dy * factor;
			
		}
		private function avoidLabels():void
		{
			dueOnEachNodePairs(avoidLabel);	
		}
		private function moveNode(node:Node):void
		{
			var dx:Number = node.dx * _damperRate ;
			var dy:Number = node.dy * _damperRate ;
			
			node.dx = dx / 2;
			node.dy = dy / 2;
			
			var dist:Number = Math.sqrt(dx * dx + dy * dy );

			if(fixedDisplays[node.display] == null ){
				node.x = node.x + Math.max(-30,Math.min(30,dx));
				node.y = node.y + Math.max(-30,Math.min(30,dy));
			}
			
			maxMotion = Math.max(dist,maxMotion);
		}
		private function moveNodes():void
		{
			var prevMaxMotion:Number = maxMotion;
			maxMotion = 0;
			
			dueOnEachNode(moveNode);
			
			var motionRate:Number = 0;
			
			if(maxMotion >0)
				motionRate = prevMaxMotion / maxMotion - 1;
			else
				motionRate = 0;
				
			damp(motionRate);
		}
		private function damp(motionRate:Number):void {
			if(_damperOn ) {
				if( motionRate <= 0.001){
					if((maxMotion < 0.2|| (maxMotion>1 && _damperRate < 0.9)) && _damperRate > 0.01)
						_damperRate-=0.01;
					else if (maxMotion < 0.4 && _damperRate > 0.003)
						_damperRate-= 0.003;
					else if (_damperRate > 0.001)
						_damperRate -= 0.0001;
				}
			}
			if( maxMotion < MontionLimit && _damperOn ){
				_damperRate = 0;
			}
		}
		public function resetDamper():void
		{
			_damperOn = true;
			_damperRate = 1.0;
		}
		
		public function addFixedDisplay(display:UIComponent):void
		{
			fixedDisplays[display] = display;
		}
		public function removeFixedDisplay(display:UIComponent):void
		{
			delete fixedDisplays[display];
		}
		
	}
}